package cn.iocoder.yudao.webapp;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Set;
import java.util.regex.Matcher;

import static cn.iocoder.yudao.framework.constant.ConfigConstants.SYSTEM_USER_DIR;
import static java.io.File.separator;

/**
 * <pre>
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 * 项目包名修改器，一键替换 Maven 的 groupId、artifactId，项目的 package 等
 * 通过修改 groupIdNew、artifactIdNew、projectBaseDirNew 三个变量
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 * </pre>
 *
 * @author 芋道源码
 * @author 山野羡民（1032694760@qq.com）
 */
@Slf4j
public class ProjectPackageReactor {
    private static final String OLD_GROUP_ID = "cn.iocoder.boot";
    private static final String OLD_ARTIFACT_ID = "yudao";
    private static final String OLD_PACKAGE_NAME = "cn.iocoder.yudao";
    private static final String OLD_TITLE = "芋道快速开发框架（羡民版）";
    // TODO ========== 配置，需要你手动修改 ==========
    private static final String NEW_GROUP_ID = "cn.iocoder.boot";
    private static final String NEW_ARTIFACT_ID = "yudao";
    private static final String NEW_PACKAGE_NAME = "cn.iocoder.yudao";
    private static final String NEW_TITLE = "芋道快速开发框架（羡民版）";

    /**
     * 白名单文件（支持前缀及后缀匹配），不进行内容替换，直接复制
     */
    private static final Set<String> COPY_FILES = CollUtil.newHashSet(
            ".gif", ".jpg", ".jpeg", ".svg", ".png", ".ico", // 图片
            ".mp4", ".3gp", ".m3u", ".wav", ".mp3", // 音频视频
            ".eot", ".woff2", ".woff", ".ttf",  // 字体
            ".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx",  // 文档
            ".db", ".sqlite", ".xdb", // 数据库，如IP归属库
            ".jar", ".war", // 三方包
            ".csv", ".json" // 其他，如行政区域数据、银行编码数据
    );

    /**
     * 白名单文件（支持前缀及后缀匹配），不进行复制，不作为项目文件
     */
    private static final Set<String> IGNORE_FILES = CollUtil.newHashSet(
            ".idea", "target", ".iml", ".classpath", ".project", ".settings", ".flattened-pom.xml", // IDE 生成的
            "dist", "node_modules", "package-lock.json", "pnpm-lock.yaml", // Node 生成的
            ".git", ".log", "logs", "upload", "attachment", "myapps" // 其他
    );

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        String projectBaseDir = getProjectBaseDir();
        log.info("[原项目路劲改地址 ({})]", projectBaseDir);

        // 一键改名后，“新”项目所在的目录
        String projectBaseDirNew = projectBaseDir.replaceAll(OLD_ARTIFACT_ID, NEW_ARTIFACT_ID) + "-new";
        log.info("[检测新项目目录 ({})是否存在]", projectBaseDirNew);
        if (FileUtil.exist(projectBaseDirNew)) {
            log.error("[新项目目录检测 ({})已存在，请更改配置后重试]", projectBaseDirNew);
            return;
        }
        // 如果新目录中存在 PACKAGE_NAME，ARTIFACT_ID 等关键字，路径会被替换，导致生成的文件不在预期目录
        if (StrUtil.containsAny(projectBaseDirNew, OLD_PACKAGE_NAME)) {
            log.error("[新项目目录 `projectBaseDirNew` 检测 ({}) 存在冲突名称「{}」，请更改配置后重试]",
                    projectBaseDirNew, OLD_PACKAGE_NAME);
            return;
        }
        log.info("[完成新项目目录检测，新项目路径地址 ({})]", projectBaseDirNew);
        log.info("[即将获取需要重写的文件，预计需要 10-50 秒]");
        Collection<File> files = listFiles(projectBaseDir);
        log.info("[需要重写的文件数量：{}，预计需要 0.3-5 分钟]", files.size());
        // 写入文件
        files.forEach(file -> {
            // 如果是白名单的文件类型，不进行重写，直接拷贝
            boolean needCopy = false;
            for (String copyFile : COPY_FILES) {
                if (file.getName().startsWith(copyFile) || file.getName().endsWith(copyFile)) {
                    needCopy = true;
                    break;
                }
            }
            log.info("[文件：{}，是否直接拷贝：{}]", file.getAbsolutePath(), needCopy);
            if (needCopy) {
                copyFile(file, projectBaseDir, projectBaseDirNew);
                return;
            }
            // 如果非白名单的文件类型，重写内容，在生成文件
            String content = replaceFileContent(file);
            writeFile(file, content, projectBaseDir, projectBaseDirNew);
        });
        log.info("[重写完成]共耗时：{} 秒", (System.currentTimeMillis() - start) / 1000);
        log.warn("[注意事项]若已初始化了数据库表，需把数据库表“infra_file_config”中的“config”及“QRTZ_JOB_DETAILS”中字段“JOB_CLASS_NAME”的值" +
                 "“{}.***”改为“{}.***”", OLD_PACKAGE_NAME, NEW_PACKAGE_NAME);
    }

    private static String getProjectBaseDir() {
        String baseDir = SYSTEM_USER_DIR;
        if (StrUtil.isEmpty(baseDir)) {
            throw new NullPointerException("项目基础路径不存在");
        }
        return baseDir;
    }

    private static Collection<File> listFiles(String projectBaseDir) {
        // 忽略 IDEA、Node 编译出来的文件、Git 自身的文件
        return FileUtil.loopFiles(projectBaseDir, file -> {
            String path = file.getAbsolutePath();
            for (String ignoreFile : IGNORE_FILES) {
                if (path.contains(File.separator + ignoreFile + File.separator)
                    || file.getName().equals(ignoreFile)
                    || file.getName().endsWith(ignoreFile)) {
                    log.info("[文件：{} 已忽略]", path);
                    return false;
                }
            }
            log.info("[文件：{} 未忽略]", path);
            return true;
        });
    }

    private static String replaceFileContent(File file) {
        String content = FileUtil.readString(file, StandardCharsets.UTF_8);
        // 执行文件内容都重写
        return content.replaceAll(OLD_GROUP_ID, ProjectPackageReactor.NEW_GROUP_ID)
                .replaceAll(OLD_PACKAGE_NAME, ProjectPackageReactor.NEW_PACKAGE_NAME)
                .replaceAll(OLD_ARTIFACT_ID, ProjectPackageReactor.NEW_ARTIFACT_ID) // 必须放在最后替换，因为 ARTIFACT_ID 太短！
                .replaceAll(StrUtil.upperFirst(OLD_ARTIFACT_ID), StrUtil.upperFirst(ProjectPackageReactor.NEW_ARTIFACT_ID))
                .replaceAll(OLD_TITLE, ProjectPackageReactor.NEW_TITLE);
    }

    private static void writeFile(File file, String fileContent, String projectBaseDir, String projectBaseDirNew) {
        String newPath = buildNewFilePath(file, projectBaseDir, projectBaseDirNew);
        FileUtil.writeUtf8String(fileContent, newPath);
    }

    private static void copyFile(File file, String projectBaseDir, String projectBaseDirNew) {
        String newPath = buildNewFilePath(file, projectBaseDir, projectBaseDirNew);
        FileUtil.copyFile(file, new File(newPath));
    }

    private static String buildNewFilePath(File file, String projectBaseDir, String projectBaseDirNew) {
        return file.getPath().replace(projectBaseDir, projectBaseDirNew) // 新目录
                .replace(OLD_PACKAGE_NAME.replaceAll("\\.", Matcher.quoteReplacement(separator)),
                        ProjectPackageReactor.NEW_PACKAGE_NAME.replaceAll("\\.", Matcher.quoteReplacement(separator)))
                .replace(OLD_ARTIFACT_ID, ProjectPackageReactor.NEW_ARTIFACT_ID) //
                .replaceAll(StrUtil.upperFirst(OLD_ARTIFACT_ID), StrUtil.upperFirst(ProjectPackageReactor.NEW_ARTIFACT_ID));
    }

}
